/**
 * File: RollingFileSplitProcessor.java
 * Created by: mhaimel
 * Created on: 22 Apr 2009
 * CVS:  $Id: RollingFileSplitProcessor.java,v 1.1 2009/06/19 13:02:49 mhaimel Exp $
 */
package uk.ac.ebi.curtain.utils.file.impl;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.CountingOutputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import uk.ac.ebi.curtain.utils.CurtainUncheckedException;
import uk.ac.ebi.curtain.utils.data.FileType;
import uk.ac.ebi.curtain.utils.file.SplitUtil.SplitFileProcessor;
import uk.ac.ebi.curtain.utils.io.impl.FileIO;

/**
 * @author mhaimel
 *
 */
public class RollingFileSplitProcessor implements SplitFileProcessor,Closeable{
	private Log log = null;
	private List<File> fileList = new ArrayList<File>();
	private final long maxFileLenth;
	private int fileCnt = 0;
	private final File baseDir;
	private final PrintWriter[] outArr;
	private final CountingOutputStream[] cntArr;
	private final File[] fileArr;
	private final String fileBaseName;
	private boolean createFiles = true;
	private final FileType fileType;
	
	public RollingFileSplitProcessor(FileType fileType, File baseDir,String fileBaseName, int nFiles, long maxFileLenth) {
		this.baseDir = baseDir;
		this.fileType = fileType;
		this.fileBaseName = fileBaseName;
		this.outArr = new PrintWriter[nFiles];
		this.cntArr = new CountingOutputStream[nFiles];
		this.fileArr = new File[nFiles];
		this.maxFileLenth = maxFileLenth;
		this.createFiles = true;
		this.fileCnt = 0;
		getLog().debug("Create new Rolling file appender to write to " 
				+ nFiles 
				+ " files of max size " 
				+ maxFileLenth 
				+ " with base name "
				+ fileBaseName
				+ " into base dir " + baseDir);
	}
	
	/**
	 * @return All output Files in the iterating order e.g. A-1, B-1, A-2, B-2
	 */
	public List<File> getFileList(){
		return this.fileList;
	}

	@Override
	public boolean processLine(String line, long lineNumber,long entryNumber, boolean startOfNewEntry) {
		int fileNumber = getFileNumber(entryNumber);
		try {
			PrintWriter pw = getOutput(fileNumber, startOfNewEntry);
			pw.println(line);
			return true;
		} catch (Exception e) {
			String msg = "Problems at writing to file " 
				+ String.valueOf(fileArr[fileNumber]) 
	            + " at lineNumber("
	            + lineNumber
	            + "); entryNumber("
	            + entryNumber
	            + "); startOfNewEntry("
	            + startOfNewEntry
	            + ") with current line >"
	            + line
	            + "<";
			throw new CurtainUncheckedException(
					msg,e);
		}
	}
	
	protected int getFileNumber(long entryNumber) {
		return (int) entryNumber%outArr.length;
	}

	private PrintWriter getOutput(int fileNumber,boolean startOfNewEntry) {
		if(startOfNewEntry){
			checkOutput(fileNumber);
		}
		return getOutput(fileNumber);
	}
	
	private PrintWriter getOutput(int fileNumber) {
		return outArr[fileNumber];
	}

	private void checkOutput(int fileNumber) {
		if(!this.createFiles && needNewFile(fileNumber)){
			this.createFiles = true;
		}
		// only create new files, if its 
		// - the first file
		// - start of entry
		if(this.createFiles && fileNumber == 0){
			createNewFiles();
		}
	}

	protected void createNewFiles() {
		getLog().debug("Close old and create new Files ... ");
		closeQuietly();
		++this.fileCnt;
		for(int i = 0; i < outArr.length; ++i){
			// file_1-1.fastq
			String fileName = fileBaseName+"_"+i+"-"+this.fileCnt;
			fileName = this.fileType.addPostfix(fileName);
			File file = new File(baseDir,fileName);
			getLog().debug("Create new File " + file + " ...");
			fileList.add(file);
			try {
				fileArr[i] = file;
				cntArr[i] = new FileIO(file).getCountingOutputStream();
				outArr[i] = new PrintWriter(new OutputStreamWriter(cntArr[i]));
			} catch (IOException e) {
				throw new CurtainUncheckedException("Problems creating File " + file,e);
			}
		}
		this.createFiles = false;
	}
	
	private Log getLog() {
		if(null == log){
			log = LogFactory.getLog(this.getClass());
		}
		return log;
	}

	protected boolean needNewFile(int fileNumber) {
		CountingOutputStream cntStream = cntArr[fileNumber];
		// byteCount is not 100% accurate (because of buffering), 
		// but close enough for what we need it.  
		return null == cntStream || cntStream.getByteCount() >= maxFileLenth;
	}

	private void close(int i){
		IOUtils.closeQuietly(outArr[i]);
		outArr[i] = null;
		IOUtils.closeQuietly(cntArr[i]);
		cntArr[i] = null;
		fileArr[i] = null;
	}

	private void closeQuietly(){
		for(int i = 0; i < outArr.length; ++i){
			close(i);
		}			
	}
	
	@Override
	public void close() throws IOException {
		closeQuietly();
	}	
	
}
